import {
  Component,
  OnInit,
  ViewEncapsulation,
  ChangeDetectionStrategy,
  Input,
  ViewContainerRef,
  ElementRef,
  ChangeDetectorRef,
  OnDestroy,
  ViewChild,
  TemplateRef,
  Output,
  EventEmitter,
  forwardRef,
  SimpleChanges,
  OnChanges,
  Renderer2,
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Overlay, OverlayConfig } from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal';
import { Subject } from 'rxjs';
import { takeUntil, tap, filter } from 'rxjs/operators';
import { cloneDeep as _cloneDeep } from 'lodash';
import {
  InputBoolean,
  OverlayService,
  I18NService,
  DateService,
  LvDateMode,
  StandardDate,
  LvConfig,
  isEmpty,
  LvSizeLD,
  showItem,
  ExtendOverlayConfig,
} from '../core/api';
import { LvDateTimeOption, LvYearsOperate } from './interface';
import { ADJUST_POSITION_MAP, getPosition } from './date-picker-position';

@Component({
  selector: 'lv-date-picker',
  exportAs: 'lvDatePicker',
  templateUrl: './date-picker.component.html',
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  animations: [showItem],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef((): typeof DatePickerComponent => DatePickerComponent),
      multi: true,
    },
  ],
  host: {
    '[class.lv-date-picker]': 'true',
    '[class.lv-date-picker-inline]': 'lvInline',
    '[class.lv-date-picker-size-large]': 'lvSize === "large"',
  },
})
export class DatePickerComponent implements ControlValueAccessor, OnInit, OnChanges, OnDestroy {
  @Input() lvPanelClass: string = '';

  @Input() lvSize: LvSizeLD = 'default';

  @Input() @InputBoolean() lvInline = false; // 内联展示模式

  @Input() lvPickerMode: LvDateMode = 'date';

  @Input() @InputBoolean() lvDisabled = false; // 是否禁用

  @Input() @InputBoolean() lvShowNowButton = true; // 是否显示'此刻'按钮

  @Input() lvDisabledDate: (date: Date) => boolean; // 禁用日期，若配置了lvTimezoneOffset,disabled返回的是面板显示时间

  @Input() @InputBoolean() lvShowTodayButton = true; // 是否显示'今天'按钮

  @Output() lvOnDateChange = new EventEmitter<any>(); // 面板初始化、通过面板交互动作选中日期触发

  @Output() lvOpenChange = new EventEmitter<boolean>(); // 切换面板回调函数

  @Input() @InputBoolean() lvOnlyShowActiveCell = false; // 是否只显示当前状态的单元格

  @Input() @InputBoolean() lvPanelTitleReadonly = false; // 面板头部标题是否可点击

  @Input() lvFooterExtra: TemplateRef<object> | null = null;

  @Output() lvActiveDateChange = new EventEmitter<any>();

  @Input() lvShowTime: boolean | LvDateTimeOption = false; // 若配置了lvTimezoneOffset,disabled返回的是面板显示时间

  @Input() lvCalendarCell: TemplateRef<Date> | null = null; // 自定义日期单元格

  @Input() lvCalendarYearCell: TemplateRef<Date> | null = null; // 自定义年份单元格

  @Input() lvPlaceholder = this._i18n.get('selectPlaceholder');

  @Input() lvCalendarMonthCell: TemplateRef<Date> | null = null; // 自定义月份单元格

  @Input() @InputBoolean() lvReadonly = LvConfig.datePickerOptions.lvReadonly; // 支持输入 NEED_DO: 对Angular DatePipe的format格式支持有问题

  @ViewChild('datePanelTpl', { static: true }) _datePanelTpl: TemplateRef<any>;

  @Input() @InputBoolean() lvShowClear = LvConfig.datePickerOptions.lvShowClear; // 是否支持清除

  @Input() lvTimezoneOffset: number = LvConfig.datePickerOptions.lvTimezoneOffset; // 本地时间与格林威治标准时间 (GMT) 的分钟差

  @Input() lvCalendarYearOperate: LvYearsOperate = { show: true, disableChange: false }; // 面板头部年份操作

  @Input() lvCalendarMonthOperate: LvYearsOperate = { show: true, disableChange: false }; // 面板头部月份操作

  @Input() lvFormat: string | ((date: Date) => string) = LvConfig.datePickerOptions.lvFormat; // 日期格式

  @Input() @InputBoolean() lvBackfillDisabledDate = LvConfig.datePickerOptions.lvBackfillDisabledDate; // 回填已禁用的时间点

  @Input() @InputBoolean() lvNowAsBaseTime =
    LvConfig.datePickerOptions.lvNowAsBaseTime === undefined ? true : LvConfig.datePickerOptions.lvNowAsBaseTime; // 是否以当前时间作为基准时间，否则以零点作为基准时间

  public overlayRef;
  public originValue;
  public _value: Date;
  public displayValue;
  public showTimeOriginValue; // 配置showTime时，保存打开面板后的初始值，用于外部点击关闭时恢复初始值
  public open: boolean = false;
  public animationState = 'void';

  public _open$ = new Subject<boolean>();
  private _initValue;
  private _direction;
  private _isClear = false; // 带时间选择时，用于记录在下拉面板打开过程中是否点击删除
  private _destroy$ = new Subject<void>();
  private _refresh$ = new Subject<void>();
  private _openState$ = new Subject<boolean>();
  constructor(
    private _viewContainerRef: ViewContainerRef,
    private _overlayService: OverlayService,
    private _overlay: Overlay,
    private _elementRef: ElementRef,
    private _renderer: Renderer2,
    private _cdr: ChangeDetectorRef,
    private _i18n: I18NService,
    public dateService: DateService,
  ) {}

  get empty(): boolean {
    return isEmpty(this.displayValue);
  }

  onFocus(): void {
    this.toggleOpen();
  }

  setRefresh(): void {
    this._refresh$.next();
  }

  registerOnChange(fn: any): void {
    this._onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this._onTouch = fn;
  }

  activeDateChange(e): void {
    this.lvActiveDateChange.emit(e);
  }

  // 关闭下拉
  close(): void {
    if (this._existOverlayRef()) {
      this.overlayRef.close();
    }
  }

  toggleOpen(): void {
    if (!this.lvDisabled) {
      this._open$.next((this.open = !this.open));
    }
  }

  _existOverlayRef(): any {
    return this.overlayRef?.overlayRef.hasAttached();
  }

  // 订阅更新视图
  _subRefresh(): void {
    this._refresh$.pipe(takeUntil(this._destroy$)).subscribe((): void => this._cdr.markForCheck());
  }

  clear(): void {
    this._isClear = true;
    this.displayValue = '';
    this.originValue = null;
    this._value = null;
    this._onChange(this._value);
  }

  onInputChange(value: string): void {
    const date = this.checkDateValid(value);
    if (date) {
      this._value = date;
      this.dateChange(date);
    }
  }

  beforeClose(): void {
    if (this._direction === 'up') {
      this.animationState = 'hideUp';
    } else {
      this.animationState = 'hideDown';
    }
    this._cdr.markForCheck();
  }

  ngOnDestroy(): void {
    if (this.overlayRef?.overlayRef.hasAttached()) {
      this.overlayRef.overlayRef.dispose();
    }

    this._destroy$.next();
    this._destroy$.complete();
  }

  _getOffsetDate(value): any {
    return this.lvTimezoneOffset || this.lvTimezoneOffset === 0
      ? this.dateService.getTimezoneOffsetDate(value, this.lvTimezoneOffset, true)
      : value;
  }

  ngOnInit(): void {
    // 默认格式化处理
    if (!this.lvFormat) {
      this.initFormat();
    } else if (typeof this.lvFormat === 'function') {
      this.lvReadonly = true;
    }

    this._subOpen();
    this._subRefresh();
  }

  onAnimationDone(event): void {
    const isUp = event.fromState === 'showUp' && event.toState === 'hideUp';
    const isDown = event.fromState === 'showDown' && event.toState === 'hideDown';
    if (isUp || isDown) {
      this._openState$.next(false);
    }
  }

  checkDate(): void {
    if (this._value && this.checkDateDisableStatus(this._value) && !this.lvBackfillDisabledDate) {
      this._value = null;
      this.displayValue = '';
      this.originValue = null;
      this._onChange(this._value);
    }
  }

  initValue(value): void {
    this._initValue = value;
    this.displayValue = this.dateService.format(value, this.lvFormat, this.lvTimezoneOffset);
    const offsetDate = this._getOffsetDate(value);
    this._value = offsetDate;
    this.originValue = _cloneDeep(offsetDate);
    this._cdr.markForCheck();
  }

  // 校验输入的日期是否符合格式要求
  checkDateValid(value: string): null | Date {
    // lvFormat为function时不做输入的校验，故lvFormat断言为string
    const date = this.dateService.parseDate(value, this.lvFormat as string);
    if (
      !this.dateService.isValidDate(date) ||
      value !== this.dateService.format(date, this.lvFormat) ||
      this.checkDateDisableStatus(date)
    ) {
      return null;
    }
    return date;
  }

  // 订阅切换面板的流
  _subOpen(): void {
    this._open$
      .pipe(
        tap((res: boolean): void => {
          this.open = res;
          if (!res) {
            this.close();
            this.setRefresh();
          }
        }), // 关闭
        filter((res): any => res), // 打开
        takeUntil(this._destroy$),
      )
      .subscribe((): void => {
        this._openPanelOverlay();
      });
  }

  ngOnChanges(changes: SimpleChanges): void {
    if (changes.lvPickerMode) {
      // 默认格式化处理
      if (!this.lvFormat) {
        this.initFormat();
      }
      this.displayValue = this.dateService.format(this._value, this.lvFormat);
      this.originValue = _cloneDeep(this._value);
    }

    if (changes.lvTimezoneOffset && !changes.lvTimezoneOffset.firstChange) {
      this.initValue(this._initValue);
    }
  }

  initFormat(): void {
    switch (this.lvPickerMode) {
      case 'date':
        if (this.lvShowTime) {
          this.lvFormat = 'yyyy-MM-dd HH:mm:ss';
        } else {
          this.lvFormat = 'yyyy-MM-dd';
        }
        break;
      case 'month':
        this.lvFormat = 'yyyy-MM';
        break;
      case 'year':
        this.lvFormat = 'yyyy';
        break;
      default:
        this.lvFormat = 'yyyy-MM-dd';
    }
  }

  onBlur(): void {
    if (!this.lvReadonly) {
      this.displayValue = '';

      if (this._value) {
        // this._value 本地时区的panel显示值，需要找到本地时区和offset的差再传到format的第一个参数
        const differ = this.lvTimezoneOffset ? this._value.getTimezoneOffset() - this.lvTimezoneOffset : 0;
        this.displayValue = this.dateService.format(
          new Date(this._value.getTime() - differ * 60 * 1000),
          this.lvFormat,
          this.lvTimezoneOffset,
        );
      }
    }
  }

  writeValue(value: any): void {
    if (value instanceof Date) {
      const offsetDate = this._getOffsetDate(value);
      if (this.checkDateDisableStatus(offsetDate) && !this.lvBackfillDisabledDate) {
        // eslint-disable-next-line no-console
        console.error('DatePicker component initialization value is disabled.');
      } else {
        this.initValue(value);
      }
    } else if (value === null) {
      this.initValue(null);
    } else {
      if (value) {
        // eslint-disable-next-line no-console
        console.error('The format of date is incorrect.');
      }
    }
  }

  afterOpen(overlayInstance): void {
    // 获取打开的方向
    overlayInstance.overlayRef._positionStrategy.positionChanges.subscribe((e): void => {
      const _position = getPosition(e.connectionPair);
      if (_position === 'topLeft' || _position === 'topCenter' || _position === 'topRight') {
        this.animationState = 'showUp';
        this._direction = 'up';
      } else {
        this.animationState = 'showDown';
        this._direction = 'down';
      }
      this._cdr.markForCheck();
    });
  }

  dateChange(date): void {
    this.lvOnDateChange.emit(date);
    if (date && date !== this.originValue && !this.checkDateDisableStatus(date)) {
      this.originValue = _cloneDeep(date);
      const offsetDate =
        this.lvTimezoneOffset || this.lvTimezoneOffset === 0
          ? this.dateService.getTimezoneOffsetDate(_cloneDeep(this._value), this.lvTimezoneOffset)
          : _cloneDeep(this._value);
      this.displayValue = this.dateService.format(offsetDate, this.lvFormat, this.lvTimezoneOffset);
      this._initValue = offsetDate;
      this._onChange(offsetDate);
      this._cdr.markForCheck();
    }
  }

  // 打开下拉panel overlay
  _openPanelOverlay(): void {
    if (!this._existOverlayRef()) {
      const config = new OverlayConfig({
        hasBackdrop: false,
        scrollStrategy: this._overlay.scrollStrategies.block(),
        positionStrategy: this._overlay
          .position()
          .flexibleConnectedTo(this._elementRef)
          .withPositions(ADJUST_POSITION_MAP),
      });
      const extendConfig = this.getExtendConfig();

      const portal = new TemplatePortal(this._datePanelTpl, this._viewContainerRef);
      this.overlayRef = this._overlayService.open(portal, config, extendConfig);
      this.lvOnDateChange.emit(this._value || new Date()); // 初始化的时候面板会展示今天
      this.setRefresh();
    }
  }

  checkDateDisableStatus(v): boolean {
    let isDateDisabled = false;
    let isTimeDisabled = false;
    const date = v ? new StandardDate(v) : null;

    if (this.lvDisabledDate) {
      isDateDisabled = this.lvDisabledDate(date.localDate);
    }
    if (date && this.lvPickerMode === 'date' && typeof this.lvShowTime !== 'boolean') {
      const hours = date.getHours();
      const minutes = date.getMinutes();
      const seconds = date.getSeconds();
      if (this.lvShowTime.disabledHour) {
        isTimeDisabled = this.lvShowTime.disabledHour(hours);
      }
      if (!isTimeDisabled && this.lvShowTime.disabledMinute) {
        isTimeDisabled = this.lvShowTime.disabledMinute(hours, minutes);
      }
      if (!isTimeDisabled && this.lvShowTime.disabledSecond) {
        isTimeDisabled = this.lvShowTime.disabledSecond(hours, minutes, seconds);
      }
    }
    return !date || isDateDisabled || isTimeDisabled;
  }

  getExtendConfig(): ExtendOverlayConfig {
    return {
      internalTriggerClose: this._internalTriggerClose,
      afterOpen: this._afterOpen,
      beforeClose: this._beforeClose,
      afterClose: this._afterClose,
    };
  }

  _internalTriggerClose = (): void => {
    if (this.lvShowTime && !this._isClear) {
      this._value = _cloneDeep(this.showTimeOriginValue);
      this.originValue = _cloneDeep(this.showTimeOriginValue);
      const offsetDate =
        this.lvTimezoneOffset || this.lvTimezoneOffset === 0
          ? this.dateService.getTimezoneOffsetDate(_cloneDeep(this._value), this.lvTimezoneOffset)
          : _cloneDeep(this._value);
      this.displayValue = this.dateService.format(offsetDate, this.lvFormat, this.lvTimezoneOffset);
      this._initValue = offsetDate;
      this._onChange(offsetDate);
      this._cdr.markForCheck();
    }
    if (this.lvShowTime && this._isClear) {
      this.clear();
    }
    this._open$.next(false);
  };

  _afterOpen = (overlayInstance): void => {
    if (this.lvShowTime) {
      this._isClear = false;
      this.showTimeOriginValue = _cloneDeep(this._value);
    }
    // 设置z-index
    this._renderer.addClass(overlayInstance.overlayRef.hostElement, 'lv-date-z-index');
    this.afterOpen(overlayInstance);
    this.lvOpenChange.emit(this.open);
  };

  _beforeClose = (): Promise<void> => {
    this.checkDate();
    return new Promise<void>((resolve): void => {
      this.beforeClose();
      const closesubscribe = this._openState$.subscribe((open): void => {
        if (!open) {
          closesubscribe.unsubscribe();
          resolve();
        }
      });
    });
  };

  _afterClose = (): void => {
    this.lvOpenChange.emit(this.open);
  };

  private _onTouch: any = (): void => {};

  private _onChange: any = (): void => {};
}
